/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <asm/byteorder.h>
#include <linux/interrupt.h>
#include <linux/firmware.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/pm.h>
#include <linux/list.h>

#include <alga/alga.h>
#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/pp.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/cm.h>

#include "mc.h"
#include "rlc.h"
#include "ih.h"
#include "fence.h"
#include "ring.h"
#include "dmas.h"
#include "ba.h"
#include "cps.h"
#include "gpu.h"
#include "drv.h"

#include "hdp.h"
#include "bif.h"
#include "smc.h"

#include "regs.h"

static void gfx_mgcg_dis(struct pci_dev *dev)
{
	rlc_mgcg_dis(dev);	
	cps_ls_dis(dev);
	gpu_mgcg_dis(dev);
	rlc_serdes_mgcg_dis(dev);
}

static void gfx_mgcg_ena(struct pci_dev *dev)
{
	gpu_mgcg_ena(dev);
	cps_ls_ena(dev);
	rlc_mgcg_ena(dev);
	rlc_serdes_mgcg_ena(dev);
}

static void gfx_cgcg_dis(struct pci_dev *dev)
{
	u32 cur;
	u32 want;

	cur = rr32(dev, RLC_CGCG_CGLS_CTL);

	gpu_3d_ring_intr_idle_dis(dev);

	/* XXX: ??? */
	rr32(dev, CB_CGTT_SCLK_CTL);
	rr32(dev, CB_CGTT_SCLK_CTL);
	rr32(dev, CB_CGTT_SCLK_CTL);
	rr32(dev, CB_CGTT_SCLK_CTL);

	want = cur & ~(RCCC_CGCG_ENA | RCCC_CGLS_ENA);
	if (cur != want)
		wr32(dev, want, RLC_CGCG_CGLS_CTL);
}

static __maybe_unused void gfx_cgcg_ena(struct pci_dev *dev)
{
	u32 cur;
	u32 want;

	cur = rr32(dev, RLC_CGCG_CGLS_CTL);

	/*
	 * FIXME: weird since this code is supposed to run in a critical
	 * section where it is disabled. Since it is not supported on
	 * southern islands, we let it remain here
	 */
	gpu_3d_ring_intr_idle_ena(dev);

	rlc_cgcg_ena(dev);
	rlc_serdes_cgcg_ena(dev);

	want = cur | RCCC_CGCG_ENA | RCCC_CGLS_ENA;
	if (cur != want)
		wr32(dev, want, RLC_CGCG_CGLS_CTL);
}

static u32 uvd_r32(struct pci_dev *dev, u32 addr)
{
	wr32(dev, addr, UVD_IDX);
	return rr32(dev, UVD_DATA);
}

static void uvd_w32(struct pci_dev *dev, u32 val, u32 addr)
{
	wr32(dev, addr, UVD_IDX);
	wr32(dev, val, UVD_DATA);
}

static void uvd_internal_cg_ena(struct pci_dev *dev)
{
	u32 uvd_cgc_ctl_0;

	uvd_cgc_ctl_0 = rr32(dev, UVD_CGC_CTL_0);
	uvd_cgc_ctl_0 &= ~(UCC0_CG_DT | UCC0_CLK_OD);
	uvd_cgc_ctl_0 |= UCC0_DCM | set(UCC0_CG_DT, 1) | set(UCC0_CLK_OD, 4);

	uvd_cgc_ctl_0 |= 0x7ffff800;

	wr32(dev, uvd_cgc_ctl_0, UVD_CGC_CTL_0);
	uvd_w32(dev, 0, UVD_CGC_CTL_1);
}

static void uvd_mgcg_ena(struct pci_dev *dev)
{
	u32 uvd_cgc_mem_ctl;
	u32 cur;
	u32 want;

	/* uvd indirect access */
	uvd_cgc_mem_ctl = uvd_r32(dev, UVD_CGC_MEM_CTL);
	uvd_cgc_mem_ctl |= 0x3fff;
	uvd_w32(dev, uvd_cgc_mem_ctl, UVD_CGC_MEM_CTL);

	/* uvd direct access */
	cur = rr32(dev, UVD_CGC_CTL_0);
	want = cur | UCC0_DCM;
	if (cur != want)
		wr32(dev, want, UVD_CGC_CTL_0);

	smc_w32(dev, 0, SMC_CG_IND_START + SMC_CG_CGTT_LOCAL_0);
	smc_w32(dev, 0, SMC_CG_IND_START + SMC_CG_CGTT_LOCAL_1);
}

void cg_ena(struct pci_dev *dev)
{
	gpu_3d_ring_intr_idle_dis(dev);
	gfx_mgcg_ena(dev);
	/*
	 * coarse grained clock gating for the gfx set of blocks is not yet
	 * working, force disable for now 
	 */
	gfx_cgcg_dis(dev); 
	
	gpu_3d_ring_intr_idle_ena(dev);

	mc_mgcg_ena(dev);
	mc_ls_ena(dev);

	dmas_mgcg_ena(dev);

	bif_mgls_ena(dev);

	hdp_mgcg_ena(dev);
	hdp_ls_ena(dev);

	/* we do enable it, even if the block is not used */
	uvd_mgcg_ena(dev);
	uvd_internal_cg_ena(dev);
}

void cg_dis(struct pci_dev *dev)
{
	gpu_3d_ring_intr_idle_dis(dev);
	gfx_cgcg_dis(dev); 
	gfx_mgcg_dis(dev);
	gpu_3d_ring_intr_idle_ena(dev);

	mc_mgcg_dis(dev);
	mc_ls_dis(dev);

	dmas_mgcg_dis(dev);

	bif_mgls_dis(dev);

	hdp_mgcg_dis(dev);
	hdp_ls_dis(dev);
}

#define PCIE_BUS_CLK	10000			/* kHz units */
#define TCLK		(PCIE_BUS_CLK / 10)	/* 10 kHz units */
long gpu_aux_clk_get(struct pci_dev *dev, u16 *gpu_aux_clk)
{
	u32 cg_clk_pin_ctl_0;
	u32 cg_clk_pin_ctl_1;
	struct dev_drv_data *dd;
	long r;
	u16 atb_core_ref_clk;

	cg_clk_pin_ctl_1 = rr32(dev, CG_CLK_PIN_CTL_1);
	if (cg_clk_pin_ctl_1 & CCC1_MUX_TCLK_TO_XCLK) {
		*gpu_aux_clk = TCLK;
		return 0;
	}

	dd = pci_get_drvdata(dev);

	r = atb_core_ref_clk_get(dd->atb, &atb_core_ref_clk);
	if (r == -ATB_ERR)
		return -SI_ERR;

	cg_clk_pin_ctl_0 = rr32(dev, CG_CLK_PIN_CTL_0);
	if (cg_clk_pin_ctl_0 & CCC0_XTAL_IN_DIVIDE) {
		*gpu_aux_clk = atb_core_ref_clk / 4;
		return 0;
	}
	*gpu_aux_clk = atb_core_ref_clk;
	return 0;
}
